/**********************************************************************
 *
 * NForwarder IOS 58 [USB 2.0] v1 with FAT32- and NTFS-Support
 * Narolez 2010 <hackinwhite[at]gmail[dot]com>
 *
 * Based on WiiMC-Forwarder by Tantric 2009-2010
 *
 * Modifications:
 *
 *   - Second search path for boot.dol added.
 *
 *   - Only mount USB partitions and search for boot.dol on USB if dol
 *     was not found on SD card. Eliminates unnecessary USB mounting.
 *
 *   - USB wake-up for better drive detection added, useful for drives 
 *     with long spin-up times. With this modification, these drives can
 *     be mounted on Wii startup. For example, if you use the forwarder 
 *     dol with Priiloader to load boot.dol on Wii startup.
 *
 *   - For first found partition, pass mountname without device number.
 *     For example "usb:" instead of "usb1:". Some USB loaders can't
 *     handle mountpoint names with device number in it correctly.
 *     
 * Requirements:
 *
 *	 - Installed IOS 58, use http://wiibrew.org/wiki/IOS58_Installer
 *     for installation.
 *
 * Code license:
 *
 *	 - GNU General Public License v3
 *     http://www.gnu.org/licenses/gpl-3.0.html
 *
 ***********************************************************************/

#include <malloc.h>
#include <stdlib.h>
#include <ogc/machine/processor.h>
#include <sys/iosupport.h>

#include "fileop.h"
#include "config.h"

#ifdef	USE_SPLASH
#include "pngu.h"
#endif	/* USE_SPLASH */

extern void __exception_closeall();
typedef void (*entrypoint) (void);
u32 load_dol_image (void *dolstart, struct __argv *argv);

#ifdef	USE_SPLASH
extern const u8		background_png[];
extern const u32	background_png_size;

void InitVideo();
void StopGX();
void Menu_Render();
void Menu_DrawImg(f32 xpos, f32 ypos, u16 width, u16 height, u8 data[], f32 degrees, f32 scaleX, f32 scaleY, u8 alphaF );
#endif	/* USE_SPLASH */

static ssize_t __out_write(struct _reent *r, int fd, const char *ptr, size_t len)
{
	return -1;
}

const devoptab_t phony_out = 
{ "stdout",0,NULL,NULL,__out_write,
  NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, NULL,0,NULL,NULL,NULL,NULL,NULL };

#ifdef	USE_AHBPROT

#define	HW_REG_BASE   0xcd800000
#define	HW_ARMIRQMASK (HW_REG_BASE + 0x03c)
#define	HW_ARMIRQFLAG (HW_REG_BASE + 0x038)

int have_hw_access()
{
	if((*(volatile unsigned int*)HW_ARMIRQMASK)&&(*(volatile unsigned int*)HW_ARMIRQFLAG))
		return 1;
  return 0;
}
#endif	/* USE_AHBPROT */

int main(int argc, char **argv)
{
	void *buffer = (void *)0x92000000;
	devoptab_list[STD_OUT] = &phony_out; // to keep libntfs happy
	devoptab_list[STD_ERR] = &phony_out; // to keep libntfs happy

	u32 version = IOS_GetVersion();
	bool iosOK;

#ifdef	USE_AHBPROT
	s32 preferred = IOS_GetPreferredVersion();
	if(version != 58 && preferred > 0 && version != (u32)preferred && have_hw_access() != 1)
	{
#else
#ifdef	FIRST_IOS
	s32 preferred = FIRST_IOS;
	if(version != preferred)
	{
#endif	/* FIRST_IOS */
#endif	/* USE_AHBPROT */

		iosOK = (IOS_ReloadIOS(preferred) < 0);
	}
	else
		iosOK = false;

#ifndef	USE_AHBPROT	
#ifdef	SECOND_IOS
	if (!iosOK)
	{
		preferred = SECOND_IOS;
		if(version != preferred)
			iosOK = (IOS_ReloadIOS(preferred) < 0);
	}
#endif	/* SECOND_IOS */
#endif	/* USE_AHBPROT */

	if (!iosOK) return 0;

	u8 i,j;
#ifdef	USE_SPLASH
	InitVideo();

	u8 *bg, a;
	int bgWidth, bgHeight;

	bg = DecodePNG(background_png, &bgWidth, &bgHeight);

	for(a = 0; a <= 255; a+=15)
	{
		Menu_DrawImg(0, 0, bgWidth, bgHeight, bg, 0, 1, 1, a);
		Menu_Render();
	}
#endif	/* USE_SPLASH */

	// Mount devices and look for the dol
	char filepath[1024] = { 0 };
	FILE *fp = NULL;

	for(i=0; i < 2; i++)
	{
		if(!MountDevice(i)) continue;
		
		for(j=0; j < MAX_DEVICES; j++)
		{
			if(part[i][j].type == 0)
				continue;
#ifdef	PATH1
			sprintf(filepath, PATH1, part[i][j].mount);
			fp = fopen(filepath, "rb");
			if(fp)
				goto found;
#endif	/* PATH1 */
#ifdef	PATH2
#ifdef	PATH1
			fclose(fp);
			fp = NULL;
			filepath[1024] = { 0 };
#endif	/* PATH1 */
			sprintf(filepath, PATH2, part[i][j].mount);
			fp = fopen(filepath, "rb");
			if(fp)
				goto found;
#endif	/* PATH2 */
		}
	}

	if(!fp)
	{
		fclose(fp);
		fp = NULL;
		UnmountAllDevices();
		USB_Deinitialize();

#ifdef	USE_SPLASH
		StopGX();
#endif	/* USE_SPLASH */

		SYS_ResetSystem(SYS_RETURNTOMENU, 0, 0);
	}

found:
	fseek (fp, 0, SEEK_END);
	int len = ftell(fp);
	fseek (fp, 0, SEEK_SET);
	fread(buffer, 1, len, fp);
	fclose (fp);
	UnmountAllDevices();
	USB_Deinitialize();

	// load entry point
	struct __argv args;
	bzero(&args, sizeof(args));
	args.argvMagic = ARGV_MAGIC;
	args.length = strlen(filepath) + 2;
	args.commandLine = (char*)malloc(args.length);
	strcpy(args.commandLine, filepath);
	args.commandLine[args.length - 1] = '\0';
	args.argc = 1;
	args.argv = &args.commandLine;
	args.endARGV = args.argv + 1;

	u32 exeEntryPointAddress = load_dol_image(buffer, &args);

	entrypoint exeEntryPoint = (entrypoint) exeEntryPointAddress;

#ifdef	USE_SPLASH
	for(a = 255; a >= 0; a-=15)
	{
		Menu_DrawImg(0, 0, bgWidth, bgHeight, bg, 0, 1, 1, a);
		Menu_Render();
	}

	StopGX();
	VIDEO_WaitVSync();
#endif	/* USE_SPLASH */

	u32 level;
	SYS_ResetSystem(SYS_SHUTDOWN, 0, 0);
	_CPU_ISR_Disable(level);
	__exception_closeall();
	exeEntryPoint();
	_CPU_ISR_Restore(level);
	return 0;
}
